
package cz.czu.pef.DataModelling.beans;

import java.io.*;
import java.util.*;

import javax.faces.context.*;

import org.ajax4jsf.model.*;
import org.richfaces.component.*;
import org.richfaces.model.*;


public class PagingExtendedTableDataModel<T> extends ExtendedDataModel implements Serializable, Modifiable
{

	private static final long serialVersionUID = -419387027238047354L;

	private DataProvider<T> dataProvider;
	private Object rowKey;
	private List<Object> wrappedKeys;
	private Map<Object, T> wrappedData;
	private Integer rowCount;
	private Integer rowIndex;
	private List<FilterField> filterFields;
	private List<SortField2> sortFields;

	public PagingExtendedTableDataModel(DataProvider<T> dataProvider)
	{
		wrappedKeys = null;
		wrappedData = new HashMap<Object, T>();
		rowCount = null;
		rowIndex = Integer.valueOf(-1);
		rowKey = null;

		this.dataProvider = dataProvider;
	}

	public Object getRowKey()
	{
		return rowKey;
	}

	public void setRowKey(Object key)
	{
		rowKey = key;
	}
	
	public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException
	{
		try
		{
			synchronized (this)
			{
				int rowC = getRowCount();
				int firstRow = ((SequenceRange) range).getFirstRow();
				int numberOfRows = ((SequenceRange) range).getRows();
				if (numberOfRows <= 0)
				{
					numberOfRows = rowC;
				}

				if (wrappedKeys != null)
				{
					Object key;
					for (Iterator<Object> it = wrappedKeys.iterator(); it.hasNext(); visitor.process(context, key, argument))
					{

						key = it.next();
						setRowKey(key);
					}

				}
				else
				{
					wrappedKeys = new ArrayList<Object>();
					int endRow = firstRow + numberOfRows;
					if (endRow > rowC)
					{
						endRow = rowC;
					}
					
					updateDataProvider();

					Object key;
					for (Iterator<T> it = loadData(firstRow, endRow).iterator(); it.hasNext(); visitor.process(context, key, argument))
					{
						if (wrappedKeys == null || wrappedData == null)
							break;
						T item = it.next();
						key = getKey(item);
						wrappedKeys.add(key);
						wrappedData.put(key, item);
					}
				}
			}
		}
		finally
		{}
	}

	protected List<T> loadData(int startRow, int endRow)
	{
		if (startRow < 0)
		{
			startRow = 0;
			throw new IllegalArgumentException((new StringBuilder()).append("Illegal start index value: ").append(startRow).toString());
		}
		int rowCount = getRowCount();
		if (endRow > rowCount)
		{
			endRow = rowCount;
			throw new IllegalArgumentException((new StringBuilder()).append("Illegal end index value: ").append(endRow).toString());
		}

		return dataProvider.getItemsByRange(startRow, endRow);
	}

	public int getRowCount()
	{
		if (rowCount == null)
		{
			rowCount = new Integer(dataProvider.getRowCount());
		}
		else
		{
			return rowCount.intValue();
		}

		return rowCount.intValue();
	}

	public Object getRowData()
	{
		if (rowKey == null)
		{
			return null;
		}

		return getObjectByKey(rowKey);
	}

	public Object getKey(T o)
	{
		return dataProvider.getKey(o);
	}

	public T getObjectByKey(Object key)
	{
		T t = wrappedData.get(key);
		if (t == null)
		{
			t = dataProvider.getItemByKey(key);
			wrappedData.put(key, t);
		}
		return t;
	}

	public int getRowIndex()
	{
		return rowIndex.intValue();
	}

	public void setRowIndex(int rowIndex)
	{
		this.rowIndex = Integer.valueOf(rowIndex);
	}

	public Object getWrappedData()
	{
		throw new UnsupportedOperationException();
	}

	public void setWrappedData(Object data)
	{
		throw new UnsupportedOperationException();
	}

	public boolean isRowAvailable()
	{
		return getRowData() != null;
	}

	public void reset()
	{
		synchronized (this)
		{
			wrappedKeys = null;
			wrappedData.clear();
			rowCount = null;
			rowIndex = Integer.valueOf(-1);
			rowKey = null;

			updateDataProvider();
		}
	}

	public DataProvider<T> getDataProvider()
	{
		return dataProvider;
	}

	public void setDataProvider(DataProvider<T> dataProvider)
	{
		this.dataProvider = dataProvider;
	}

	@Override
	public void modify(List<FilterField> filterFields, List<SortField2> sortFields)
	{
		this.filterFields = filterFields;
		this.sortFields = sortFields;

		reset();
	}

	private void updateDataProvider()
	{
		if (dataProvider instanceof Sortable2)
		{
			((Sortable2) dataProvider).setSortFields(sortFields);
		}

		if (dataProvider instanceof Filterable)
		{
			((Filterable) dataProvider).setFilterFields(filterFields);
		}
	}

}